/* Copyright   1991 Gustavus Adolphus College.  All rights reserved.
 *
 * Schematik was developed by Gustavus Adolphus College (GAC) with
 * support from NeXT Computer, Inc.  Permission to copy this software,
 * to redistribute it, and to use it for any purpose is granted,
 * subject to the following restrictions and understandings.
 *
 * 1. Any copy made of this software must include this copyright
 * notice in full.
 *
 * 2. Users of this software agree to make their best efforts (a) to
 * return to the GAC Mathematics and Computer Science Department any
 * improvements or extensions that they make, so that these may be
 * included in future releases; and (b) to inform GAC of noteworthy
 * uses of this software.
 *
 * 3. All materials developed as a consequence of the use of this
 * software shall duly acknowledge such use, in accordance with the
 * usual standards of acknowledging credit in academic research.
 *
 * 4. GAC makes no express or implied warranty or representation of
 * any kind with respect to this software, including any warranty
 * that the operation of this software will be error-free.  ANY
 * IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR
 * PURPOSE IS HEREBY DISCLAIMED.  GAC is under no obligation to
 * provide any services, by way of maintenance, update, or otherwise.
 *
 * 5. In conjunction with products arising from the use of this
 * material, there shall be no use of the name of Gustavus Adolphus
 * College nor of any adaptation thereof in any advertising,
 * promotional, or sales literature without prior written consent
 * from GAC in each case.
 */

#import "SchemeProtocol.h"
#import "../defines.h"
#import Main_h
#import PrefAgent_h
#import DocText_h
#import InteractionWin_h
#import <appkit/Matrix.h>
#import <appkit/Panel.h>
#import <appkit/Text.h>
#import <sys/signal.h>
#import <sys/socket.h>
#import <defaults.h>
#import <fcntl.h>
#import <libc.h>
#import <mach.h>
#import <NXCType.h> 
#import <string.h>
extern void NXBeep(void);

#define BUFFERSIZE		4096
#define COMMENTCHAR		';'
#define COMMENTSTRING		";"
#define ERRORAFFIRMATIVE	"y"
#define ERRORNEGATIVE		"n"
#define MAXPARAMS		64
#define PROMPTMESSAGESPLITCHAR	' '
#define QUERYAFFIRMATIVE	"y"
#define QUERYNEGATIVE		"n"
#define SCHEMEENVIRON1	"USE_SCHEMATIK_STYLE_INTERRUPTS="
#define SCHEMEENVIRON2	"USE_SCHEMATIK_STYLE_ERRORS="
#define SCHEMEENVIRON3	"USE_SCHEMATIK_STYLE_GRAPHICS="
#define SMF1A		"(message \"%s\" \""
#define SMF1B		"\")"
#define SMF2A		"(xscheme-write-message-1 xscheme-prompt (format \";Value "
#define SMF2B		": %s\" xscheme-prompt))"
#define YORN		" (y or n)? "

#define USLEEP(x) {int _usleep_mask=sigsetmask(~0);\
                   usleep((x));\
                   sigsetmask(_usleep_mask);}

static void fdHandler(int, id);
static void outputHandler(DPSTimedEntry, double, id);
static void childHandler(DPSTimedEntry, double, id);

int fromScheme,toScheme,schemePid,graphicsFD;
int inState=1,j=0,k=0;
BOOL inInputState=NO,gc=NO,sigintOK=YES;
char buffer1[BUFFERSIZE],buffer3[BUFFERSIZE];
DPSTimedEntry TE,childCheck;
NXZone *outputZone=NULL;
char *outputBuffer=NULL;
unsigned outputBufferSize=0;
char *bogusEscPHack=NULL;

@implementation SchemeProtocol

- init
{
    int sv[2],sw[2],numFDs,fd,pidChild;

DEBUG_FUNC1(DEBUGLEVEL);
    if ((socketpair(AF_UNIX,SOCK_STREAM,0,sv)<0)||(socketpair(AF_UNIX,SOCK_STREAM,0,sw)<0))
      {
        NXRunAlertPanel(ERRORTITLE,SOCKETERRORMESSAGE,NULL,NULL,NULL);
        return nil;
      }
    switch (pidChild = vfork())
      {
        case -1: NXRunAlertPanel(ERRORTITLE,FORKERRORMESSAGE,NULL,NULL,NULL);
                 return nil;
        case 0 : {
                   char *argv[MAXPARAMS],**envp;
                   char *parms=NXCopyStringBuffer([[PrefAgent new] parameters]);
                   int i;

                   argv[0]=[[PrefAgent new] executable];
                   argv[1]=strtok(parms, WHITESPACECHARS);
                   for(i=2; i<MAXPARAMS; i++)
		     argv[i]=strtok(NULL, WHITESPACECHARS);
		   argv[MAXPARAMS-1]=NULL;
                   for(i=0; environ[i]; i++);
                   envp=(char **)malloc((i+3)*sizeof(char *));
                   for(i=0; environ[i]; i++) envp[i]=environ[i];
                   envp[i++]=SCHEMEENVIRON1;
                   envp[i++]=SCHEMEENVIRON2;
                   envp[i++]=SCHEMEENVIRON3;
		   envp[i]=NULL;
                   dup2(sv[1],0);
                   dup2(sv[1],1);
                   dup2(sv[1],2);
                   dup2(sw[1],3);
                   numFDs = getdtablesize();
                   for(fd=4; fd<numFDs; fd++)
                     close(fd);
                   execve(argv[0],argv,envp);
                   perror(EXECERRORMESSAGE);
                   _exit(1);
		 }
        default: schemePid = pidChild;
                 fromScheme = toScheme = sv[0];
		 graphicsFD = sw[0];
                 fcntl(toScheme, F_SETFL, fcntl(toScheme, F_GETFL, 0)|FNDELAY);
                 fcntl(graphicsFD, F_SETFL, fcntl(graphicsFD, F_GETFL, 0)|FNDELAY);
                 DPSAddFD(fromScheme, (DPSFDProc)fdHandler, (id)self, NX_BASETHRESHOLD+1);
                 TE = (DPSTimedEntry)0;
		 interactionWin = [NXApp interactionWindow];
                 outputZone = NXCreateZone(vm_page_size, vm_page_size, YES);
                 outputBuffer = (char *)NXZoneMalloc(outputZone, 1020);
                 childCheck = DPSAddTimedEntry(10.0, (DPSTimedEntryProc)childHandler, self, NX_MODALRESPTHRESHOLD+1);
                 return self;
      }
}

- free
{
    inState=1;
    j=k=0;
    inInputState=NO;
    gc=NO;
    sigintOK=YES;
    if (bogusEscPHack!=NULL) free(bogusEscPHack);
    bogusEscPHack=NULL;
    buffer1[0] = buffer3[0] = 0;
    NXDestroyZone(outputZone);
    outputZone = NULL;
    outputBuffer = NULL;
    outputBufferSize = 0;
    return [super free];
}

@end

@implementation SchemeProtocol (SchemeProcess)

- (int)schemePid	{ return schemePid;}

- outputHandler
{
DEBUG_FUNC1(DEBUGLEVEL);
    if (outputBufferSize)
      {
        int num = write(toScheme, outputBuffer, outputBufferSize);
        if (num>0)
          {
            outputBufferSize-=num;
            memmove(outputBuffer, (outputBuffer+num), outputBufferSize);
          }
      }
    if (!TE&&(outputBufferSize>0))
      TE = DPSAddTimedEntry(0.01, (DPSTimedEntryProc)outputHandler, self, NX_MODALRESPTHRESHOLD);
    else if (TE&&!outputBufferSize)
      {
	DPSRemoveTimedEntry(TE);
	TE = (DPSTimedEntry)0;
      }
    return self;
}

- send:(STR)aString
{
DEBUG_FUNC1(DEBUGLEVEL);
    outputBuffer = (char *)NXZoneRealloc(outputZone, outputBuffer, outputBufferSize+strlen(aString)+1);
    strcpy((outputBuffer+outputBufferSize), aString);
    outputBufferSize+=strlen(aString);
    [self outputHandler];
    return self;
}

- sendNul
{
DEBUG_FUNC1(DEBUGLEVEL);
    outputBuffer = (char *)NXZoneRealloc(outputZone, outputBuffer, outputBufferSize+1);
    outputBuffer[outputBufferSize++] = '\0';
    [self outputHandler];
    return self;
}

- sendSignal:(int)aSignal
{
DEBUG_FUNC1(DEBUGLEVEL);
    switch (aSignal)
      {
        case SIGNALBREAKPOINT: kill(schemePid,aSignal); return self;
        case SIGNALABORTTOP  : if (!sigintOK) return self;
        case SIGNALABORTPREV :
        case SIGNALABORTSAME : kill(schemePid,aSignal);
      }
    USLEEP(10000);
    return [self sendNul];
}

- sendGraphicsWindow:(int)windowNum
{
    char buffer[2044];

    sprintf(buffer,"%i\n%s\n%s\n",windowNum, (NXGetDefaultValue([NXApp appName],"NXHost")?:"\0"), (NXGetDefaultValue([NXApp appName],"NXPSName")?:"\0"));
    write(graphicsFD, buffer, strlen(buffer));
    return self;
}

#include <syslog.h>

void kill_alarm(int sig)
{
    kill(schemePid, SIGKILL);
    syslog(LOG_WARNING|LOG_LOCAL1, "Scheme termination timeout (5 sec).  Killing with SIGKILL.");
}

void kill_scheme()
{
    void (*oldsigh)(int);
    union wait status;
    int ret,oldsigb;

    kill(schemePid, SIGHUP);
    oldsigb=sigblock(0);
//    if (sigmask(SIGALRM)&oldsigb)
//      oldsigb=oldsigb&(~sigmask(SIGALRM));
    sigblock(oldsigb);
    oldsigh = signal(SIGALRM, kill_alarm);
    ualarm(5000000, 0);
    ret = wait4(schemePid, &status, 0, NULL);
    if (WIFSTOPPED(status)||(ret<0))
      kill_alarm(0);
    ualarm(0, 0);
    signal(SIGALRM, oldsigh);
}

- terminate:sender withPrejudice:(BOOL)aBoolean
{
DEBUG_FUNC1(DEBUGLEVEL);
    if (!schemePid)
      return self;
    DPSRemoveTimedEntry(childCheck);
    if (TE) DPSRemoveTimedEntry(TE);
    kill_scheme();
    DPSRemoveFD(fromScheme);
    close(fromScheme);
    close(toScheme);
    close(graphicsFD);
    schemePid=0;
    if (!aBoolean)
      NXRunAlertPanel(ERRORTITLE,TERMINATIONERRORMESSAGE,NULL,NULL,NULL);
    return self;
}

- fdHandler:(int)anFD
{
    char aChar,tempBuffer[BUFFERSIZE];
    int ret,i;

DEBUG_FUNC1(DEBUGLEVEL);
    if ((ret=read(anFD, tempBuffer, BUFFERSIZE))<=0)
      return [self terminate:self withPrejudice:NO];
    for(i=0; i<ret; i++)
      {
        aChar = tempBuffer[i];
        switch (inState)
          {
            case 1 : switch (aChar)							/* Initial state */
                       {
                         case 27 : buffer1[j] = '\0'; [self sendOutput:buffer1]; j=0; inState = 2; break;
                         case 7  : NXBeep(); break;
                         default : buffer1[j++] = aChar;
                       }
                     break;
            case 2 : switch (aChar)							/* ESC sequence state */
                       {
                         case 'c' :
                         case 'o' :
                         case 'z' :
                         case 'R' : inState = 1; break;					/* nop, no string */
                         case 'w' :
                         case 'D' : k = 0; buffer3[0] = '\0'; inState = 3; break;	/* nop, with string */
                         case 'b' : [self showRunState:GARBCOLLECT]; gc = YES; inState = 1;  break;
                         case 'e' : [self showRunState:PREVIOUS]; gc = NO; inState = 1;  break;
                         case 'f' : [self showRunState:EVALUATING]; inInputState = NO; inState = 1;  break;
                         case 's' : [self showRunState:INPUTSTATE]; inInputState = YES; [self sendGraphics:"f0"]; inState = 1;  break;
                         case 'g' : sigintOK = YES; inState = 1;  break;
                         case 'i' :
                         case 'm' : k = 0; buffer3[0] = '\0'; inState = 4; break;	/* normal message */
                         case 'p' : k = 0; buffer3[0] = '\0'; inState = 5; break;	/* prompt message */
                         case 'v' : k = 0; buffer3[0] = '\0'; inState = 6; break;	/* normal value */
                         case 'n' : k = 0; buffer3[0] = '\0'; inState = 7; break;	/* yes/no query */
                         case 'B' : k = 0; buffer3[0] = '\0'; inState = 8; break;	/* error message */
                         case 'E' : k = 0; buffer3[0] = '\0'; inState = 9; break;	/* special message */
                         case 'P' : k = 0; buffer3[0] = '\0'; inState = 10; break;	/* special value */
                         case 'G' : k = 0; buffer3[0] = '\0'; inState = 11; break;	/* graphics */
                         default  : NXRunAlertPanel(PROTOCOLERROR, NONPROTOCOLERROR, NULL, NULL, NULL, aChar); inState = 1; 
                       }
                     break;
            case 3 : if (aChar==27)						// nop string
                       {
                         buffer3[k] = '\0';
                         inState = 1;  break;
                       }
                     buffer3[k++] = aChar; break;
            case 4 : if (aChar==27)						// normal message
                       {
                         buffer3[k] = '\0';
			 [self sendMessage:buffer3];
                         inState = 1;  break;
                       }
                     buffer3[k++] = aChar;  break;
            case 5 : if (aChar==27)						// prompt message
                       {
                         buffer3[k] = '\0';
			 [self sendPrompt:buffer3];
                         inState = 1;  break;
                       }
                     buffer3[k++] = aChar; break;
            case 6 : if (aChar==27)						// value
                       {
                         buffer3[k] = '\0';
			 [self sendValue:buffer3];
                         inState = 1;  break;
                       }
                     buffer3[k++] = aChar; break;
            case 7 : if (aChar==27)						// yes/no query
		       {
			 buffer3[k] = '\0';
			 if ((strlen(buffer3)>=strlen(YORN))&&
			      !NXOrderStrings((unsigned char *)(buffer3+strlen(buffer3)-strlen(YORN)),(unsigned char *)YORN,NO,-1,NULL))
			   strcpy(buffer3+strlen(buffer3)-strlen(YORN), "?");
			 [self sendQuery:buffer3];
			 inState = 1;  break;
		       }
                     buffer3[k++] = aChar; break;
            case 8 : if (aChar==27)						// error
                       {
                         buffer3[k] = '\0';
			 [self sendError:buffer3];
                         inState = 1;  break;
                       }
                     buffer3[k++] = aChar; break;
            case 9 : if (aChar==27)						// special message
                       {
                         buffer3[k] = '\0';
			 if ((strlen(buffer3)>=strlen(SMF1A)+strlen(SMF1B))&&
			      !NXOrderStrings((unsigned char *)buffer3,(unsigned char *)SMF1A,NO,strlen(SMF1A),NULL)&&
			      !NXOrderStrings((unsigned char *)(buffer3+strlen(buffer3)-strlen(SMF1B)),(unsigned char *)SMF1B,NO,-1,NULL))
                           {
			     buffer3[k-strlen(SMF1B)] = '\0';
			     [self sendMessage:(buffer3+strlen(SMF1A))];
                           }
			 else if ((bogusEscPHack!=NULL)&&
			      (strlen(buffer3)>=strlen(SMF2A)+strlen(SMF2B))&&
			      !NXOrderStrings((unsigned char *)buffer3,(unsigned char *)SMF2A,NO,strlen(SMF2A),NULL)&&
			      !NXOrderStrings((unsigned char *)(buffer3+strlen(buffer3)-strlen(SMF2B)),(unsigned char *)SMF2B,NO,-1,NULL))
                           {
//			     if (![[PrefAgent new] includeHashValues])
                               [self sendValue:bogusEscPHack];
//			     else
//			       {
//			         buffer3[k-strlen(SMF2B)] = '\0';
//			         [self sendSpecialValue:bogusEscPHack withHashCode:(buffer3+strlen(SMF2A))];
//			       }
			     free(bogusEscPHack);
			     bogusEscPHack=NULL;
			   }
                         inState = 1;  break;
                       }
                     buffer3[k++] = aChar; break;
            case 10 : if (aChar==27)						// special value
                       {
                         buffer3[k] = '\0';
			 bogusEscPHack = NXCopyStringBuffer(buffer3);
                         inState = 1;  break;
                       }
                     buffer3[k++] = aChar;  break;

            case 11: if (aChar==27)						// graphics
                       {
                         buffer3[k] = '\0';
			 [self sendGraphics:buffer3];
                         inState = 1;  break;
                       }
                     buffer3[k++] = aChar; break;
          }
      }
    if (inState==1)
      {
        buffer1[j] = '\0'; [self sendOutput:buffer1]; j = 0;
      }
    return self;
}

@end

@implementation SchemeProtocol (InteractionWindow)

- sendMessage:(STR)aString
{
    id text = [interactionWin textView];
DEBUG_FUNC1(DEBUGLEVEL);
    [text appendNewlineIfNeeded];
    return [text appendText:aString commented:YES withNewline:YES];
}

- sendQuery:(STR)aString
{
DEBUG_FUNC1(DEBUGLEVEL);
    if (NXRunAlertPanel(QUERYTITLE, aString, QUERYDEFAULT, QUERYALTERNATE, NULL)>0)
      [self send:QUERYAFFIRMATIVE];
    else
      [self send:QUERYNEGATIVE];
    return self;
}

- sendValue:(STR)aString
{
    id text = [interactionWin textView];
DEBUG_FUNC1(DEBUGLEVEL);
    [text appendNewlineIfNeeded];
    if (!*aString)
      return [text appendText:NOVALUESTRING commented:YES withNewline:YES];
    [text appendText:VALUESTRING commented:YES withNewline:NO];
    return [text appendText:aString commented:NO withNewline:YES];
}

- sendSpecialValue:(STR)aString withHashCode:(STR)hashString
{
    id text = [interactionWin textView];
DEBUG_FUNC1(DEBUGLEVEL);
    [text appendNewlineIfNeeded];
    [text appendText:"Value " commented:YES withNewline:NO];
    [text appendText:hashString commented:NO withNewline:NO];
    [text appendText:": " commented:NO withNewline:NO];
    return [text appendText:aString commented:NO withNewline:YES];
}

- sendGraphics:(STR)aString		// sent to NXApp (Main)
{
    char function;
    unsigned int windownum,width,height,mode;

    switch (aString[0])
      {
        case 'c' : sscanf(aString, "%c%d", &function, &windownum);
	           [NXApp closeGraphicsWindow:windownum];
	           break;
	case 'f' : sscanf(aString, "%c%d%d", &function, &windownum, &mode);
		   [NXApp setGraphicsWindowFlush:windownum mode:mode];
		   break;
	case 'o' : sscanf(aString, "%c%d%u%u", &function, &windownum, &width, &height);
		   [NXApp openGraphicsWindow:windownum width:width height:height];
		   break;
	case 'p' : sscanf(aString, "%c%d", &function, &windownum);
		   [NXApp printGraphicsWindow:windownum];
		   break;
	default  : NXRunAlertPanel(PROTOCOLERROR, GRAPHICSPROTOCOLERROR, NULL, NULL, NULL, aString[0]);
      }
    return self;
}

- sendOutput:(STR)aString
{
    id text = [interactionWin textView];
DEBUG_FUNC1(DEBUGLEVEL);
    if (*aString)
      return [text appendText:aString commented:[[PrefAgent new] commentOutput] withNewline:NO];
    return self;
}

- sendPrompt:(STR)aString
{
    id text = [interactionWin textView];
DEBUG_FUNC1(DEBUGLEVEL);
    [interactionWin setLevel:atoi(aString)];
    [interactionWin setPrompt:strchr(aString, PROMPTMESSAGESPLITCHAR)];
    return [text appendText:"" commented:NO withNewline:YES];
}

- sendError:(STR)aString
{
DEBUG_FUNC1(DEBUGLEVEL);
    if (NXRunAlertPanel(ERRORTITLE,ERRORMESSAGE,ERRORDEFAULT,ERRORALTERNATE,NULL,aString)>0)
      switch ([[PrefAgent new] errorAbort])
        {
	  case 0: [self sendSignal:SIGNALABORTTOP]; break;
	  case 1: [self sendSignal:SIGNALABORTSAME]; break;
	}
    else
      [self sendNul];
    [self showRunState:PREVIOUS];
    if ([[NXApp prefAgent] insertErrors])
      {
        [self sendMessage:aString];
	[interactionWin makeKeyAndOrderFront:self];
      }
    return self;
}


- showRunState:(int)aState
{
    static int prevState;

DEBUG_FUNC1(DEBUGLEVEL);
    switch (aState)
      {
        case INPUTSTATE : [interactionWin setTitle:INTERWINDOWTITLE];
	                  prevState=INPUTSTATE; break;
	case EVALUATING : [interactionWin setTitle:EVALMESSAGE];
	                  prevState=EVALUATING; break;
	case GARBCOLLECT: [interactionWin setTitle:GCMESSAGE]; break;
	case PREVIOUS   : ((prevState==INPUTSTATE)?[interactionWin setTitle:INTERWINDOWTITLE]:[interactionWin setTitle:EVALMESSAGE]);
      }
    return self;
}

@end

static void fdHandler(int anFD, id self)
{
    [self fdHandler:anFD];
}

static void outputHandler(DPSTimedEntry te, double time, id self)
{
    [self outputHandler];
}

static void childHandler(DPSTimedEntry te, double time, id self)
{
    extern char *sys_siglist[];
    union wait status;
    int ret = wait4(schemePid, &status, WNOHANG|WUNTRACED, NULL);

    if (ret>0)
      {
        if (WIFSTOPPED(status))
	  NXRunAlertPanel(SCHEMEPROCMESSAGE,PROCESSSTOPPED,NULL,NULL,NULL,status.w_stopsig,sys_siglist[status.w_stopsig]);
	else if (WIFSIGNALED(status))
	  {
	    if (NXRunAlertPanel(SCHEMEPROCMESSAGE,PROCESSSIGTERM,
	                      RESTARTDEFAULT,RESTARTALTERNATE,NULL,status.w_termsig,sys_siglist[status.w_termsig]))
	      [NXApp restartSubprocess];
	  }
	else
	  NXRunAlertPanel(SCHEMEPROCMESSAGE,PROCESSEXITTERM,NULL,NULL,NULL,status.w_retcode);
      }
}
